"""
Progress tracking utility for the data preprocessing pipeline.

This module provides progress tracking functionality for batch processing operations,
including percentage calculation, time estimation, and real-time updates.
"""

import time
from typing import Optional, Callable
from datetime import datetime, timedelta
from core.models import ProgressInfo


class ProgressTracker:
    """
    Utility class for tracking and displaying progress during batch processing.
    
    This class handles progress percentage calculation, estimated time remaining,
    and provides real-time updates during batch processing operations.
    """
    
    def __init__(self, total_combinations: int, update_callback: Optional[Callable[[ProgressInfo], None]] = None):
        """
        Initialize the ProgressTracker.
        
        Args:
            total_combinations (int): Total number of combinations to process
            update_callback (Optional[Callable]): Optional callback function for progress updates
        """
        self.total_combinations = total_combinations
        self.current_combination = 0
        self.start_time = None
        self.last_update_time = None
        self.update_callback = update_callback
        self.processing_times = []  # Store individual processing times for better estimation
        self.current_combination_id = ""
        
        # Validation
        if total_combinations <= 0:
            raise ValueError("Total combinations must be greater than 0")
    
    def start_tracking(self) -> None:
        """
        Start progress tracking.
        
        Initializes timing and resets counters for a new batch processing session.
        
        Requirements: 7.1 - display progress indicator when batch processing starts
        """
        self.start_time = time.time()
        self.last_update_time = self.start_time
        self.current_combination = 0
        self.processing_times = []
        self.current_combination_id = ""
        
        # Send initial progress update
        progress_info = self._create_progress_info()
        if self.update_callback:
            self.update_callback(progress_info)
    
    def update_progress(self, combination_id: str, processing_time: Optional[float] = None) -> ProgressInfo:
        """
        Update progress for the next combination.
        
        Args:
            combination_id (str): ID of the combination being processed
            processing_time (Optional[float]): Time taken to process the previous combination
            
        Returns:
            ProgressInfo: Current progress information
            
        Requirements: 7.2 - show percentage completion and estimated time remaining
        """
        current_time = time.time()
        
        # Record processing time if provided
        if processing_time is not None and processing_time > 0:
            self.processing_times.append(processing_time)
            
            # Keep only recent processing times for better estimation (last 10 combinations)
            if len(self.processing_times) > 10:
                self.processing_times = self.processing_times[-10:]
        
        # Update current combination
        self.current_combination += 1
        self.current_combination_id = combination_id
        self.last_update_time = current_time
        
        # Create progress info
        progress_info = self._create_progress_info()
        
        # Send progress update
        if self.update_callback:
            self.update_callback(progress_info)
        
        return progress_info
    
    def get_current_progress(self) -> ProgressInfo:
        """
        Get current progress information without updating counters.
        
        Returns:
            ProgressInfo: Current progress information
        """
        return self._create_progress_info()
    
    def _create_progress_info(self) -> ProgressInfo:
        """
        Create ProgressInfo object with current state.
        
        Returns:
            ProgressInfo: Progress information object
        """
        percentage = self._calculate_percentage()
        estimated_time_remaining = self._calculate_estimated_time_remaining()
        
        return ProgressInfo(
            current_combination=self.current_combination,
            total_combinations=self.total_combinations,
            percentage=percentage,
            estimated_time_remaining=estimated_time_remaining,
            current_combination_id=self.current_combination_id
        )
    
    def _calculate_percentage(self) -> float:
        """
        Calculate completion percentage.
        
        Returns:
            float: Completion percentage (0.0 to 100.0)
            
        Requirements: 7.2 - show percentage completion
        """
        if self.total_combinations == 0:
            return 100.0
        
        return min((self.current_combination / self.total_combinations) * 100.0, 100.0)
    
    def _calculate_estimated_time_remaining(self) -> float:
        """
        Calculate estimated time remaining in seconds.
        
        Uses average processing time from recent combinations to estimate
        remaining time based on combinations left to process.
        
        Returns:
            float: Estimated time remaining in seconds
            
        Requirements: 7.2 - show estimated time remaining
        """
        if self.current_combination == 0 or self.start_time is None:
            return 0.0
        
        remaining_combinations = self.total_combinations - self.current_combination
        
        if remaining_combinations <= 0:
            return 0.0
        
        # Use average of recent processing times if available
        if self.processing_times:
            avg_processing_time = sum(self.processing_times) / len(self.processing_times)
            return remaining_combinations * avg_processing_time
        
        # Fallback: use overall elapsed time average
        elapsed_time = time.time() - self.start_time
        if self.current_combination > 0:
            avg_time_per_combination = elapsed_time / self.current_combination
            return remaining_combinations * avg_time_per_combination
        
        return 0.0
    
    def format_progress_display(self, progress_info: Optional[ProgressInfo] = None) -> str:
        """
        Format progress information for display.
        
        Args:
            progress_info (Optional[ProgressInfo]): Progress info to format, uses current if None
            
        Returns:
            str: Formatted progress display string
            
        Requirements: 7.1, 7.2 - display progress indicator with percentage and time
        """
        if progress_info is None:
            progress_info = self.get_current_progress()
        
        # Format percentage
        percentage_str = f"{progress_info.percentage:.1f}%"
        
        # Format progress bar
        bar_width = 30
        filled_width = int((progress_info.percentage / 100.0) * bar_width)
        progress_bar = "█" * filled_width + "░" * (bar_width - filled_width)
        
        # Format time remaining
        time_remaining_str = self._format_time_duration(progress_info.estimated_time_remaining)
        
        # Format current combination info
        combination_info = f"({progress_info.current_combination}/{progress_info.total_combinations})"
        
        # Build display string
        display_parts = [
            f"Progress: {percentage_str} {combination_info}",
            f"[{progress_bar}]",
            f"ETA: {time_remaining_str}"
        ]
        
        if progress_info.current_combination_id:
            display_parts.append(f"Current: {progress_info.current_combination_id}")
        
        return " | ".join(display_parts)
    
    def _format_time_duration(self, seconds: float) -> str:
        """
        Format time duration in a human-readable format.
        
        Args:
            seconds (float): Duration in seconds
            
        Returns:
            str: Formatted time string (e.g., "2h 30m 15s", "45s", "1m 23s")
        """
        if seconds <= 0:
            return "0s"
        
        # Convert to integer seconds for formatting
        total_seconds = int(seconds)
        
        hours = total_seconds // 3600
        minutes = (total_seconds % 3600) // 60
        secs = total_seconds % 60
        
        if hours > 0:
            return f"{hours}h {minutes}m {secs}s"
        elif minutes > 0:
            return f"{minutes}m {secs}s"
        else:
            return f"{secs}s"
    
    def get_elapsed_time(self) -> float:
        """
        Get total elapsed time since tracking started.
        
        Returns:
            float: Elapsed time in seconds
        """
        if self.start_time is None:
            return 0.0
        
        return time.time() - self.start_time
    
    def get_average_processing_time(self) -> float:
        """
        Get average processing time per combination.
        
        Returns:
            float: Average processing time in seconds
        """
        if not self.processing_times:
            if self.current_combination > 0 and self.start_time is not None:
                return self.get_elapsed_time() / self.current_combination
            return 0.0
        
        return sum(self.processing_times) / len(self.processing_times)
    
    def is_complete(self) -> bool:
        """
        Check if processing is complete.
        
        Returns:
            bool: True if all combinations have been processed
        """
        return self.current_combination >= self.total_combinations